package communication.common.command;

import communication.common.business.base.*;
import communication.common.business.util.MsgDataUtil;
import communication.common.business.util.OsParamUtil;
import communication.common.vertx.net.NetManager;
import communication.common.vertx.net.ProtocolEnum;
import io.vertx.core.cli.CLI;
import io.vertx.core.cli.CommandLine;
import io.vertx.core.cli.Option;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.shell.command.CommandBuilder;
import io.vertx.ext.shell.command.CommandProcess;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 *  网络命令
 * @author mac
 */
@Slf4j
@Data
@RequiredArgsConstructor
public class NetCli implements BaseCommandCli{


    private final FcrAutoConfig fcrAutoConfig;

    private final NetManager netManager;



    @Override
    public CommandBuilder getCliBuilder() {
        // 命令例子： socket -l  10000
        CLI net = CLI.create("socket")
                .addOption(new Option().setShortName("l").setLongName("listen").setDescription("l表示监听一个端口"))
                .addOption(new Option().setShortName("r").setLongName("remote").setDescription("设置消息要发送到的地址"))
                .addOption(new Option().setShortName("hole").setLongName("hole").setDescription("向指定地址打洞"))
                .addOption(new Option().setShortName("server").setLongName("server").setDescription("设置服务端地址"))
                .addOption(new Option().setShortName("protocol").setLongName("protocol").setDescription("设置通信协议"))
                .addOption(new Option().setShortName("showConn").setLongName("showConn").setDescription("打印所有socket连接"))
                .addOption(new Option().setShortName("chat").setLongName("chat").setDescription("发送消息结构化的消息"))
                .addOption(new Option().setShortName("close").setLongName("close").setDescription("关闭socketServer,输入任意字符即可关闭"))
                .addOption(new Option().setShortName("p").setLongName("port").setDescription("本地端口号"))
                .addOption(new Option().setShortName("c").setLongName("count").setDescription("数量"));


        final AtomicReference<String>  remoteHost=new AtomicReference();
        final AtomicReference<Integer> remotePort=new AtomicReference();

        CommandBuilder commandBuilder=CommandBuilder.command(net);

        commandBuilder.processHandler(process->{

            try {
                CommandLine commandLine = process.commandLine();

                // 监听端口
                startListenPort(commandLine, process);

                // 设置接收消息的远程地址
                setRemoteReceiverAddress(commandLine,process,remoteHost,remotePort);

                // 发送结构化的消息
                sendMsg(commandLine,process,remoteHost,remotePort);

                // 设置server端地址
                resetRemoteServerAddress(commandLine ,process);

                // 设置通信协议
                resetProtocol(commandLine ,process);

                // 打印连接
                showConnect(commandLine,process);

                // 与指定address打洞
                holePunching(commandLine,process);

                // todo: 关闭socketServer
                closeSocketServer(commandLine,process, fcrAutoConfig.getProtocol(), remoteHost,remotePort);

            }catch (Exception e){
                e.printStackTrace();
                process.write("发生异常:"+e.getMessage());
                process.end();
            }
        });
        return commandBuilder;
    }

    /**
     * 打洞
     * @param commandLine
     * @param process
     */
    private void holePunching(CommandLine commandLine, CommandProcess process) throws Exception {
        // hole的参数个数   远端ip:远端端口:本地要进行打洞的起始端口号, 例如  154.23.11.10:2345
        String hole = commandLine.getOptionValue("hole");

        if( StringUtils.isEmpty(hole)){
            return;
        }

        // 打洞用起始端口号
        String p = commandLine.getOptionValue("p");
        if( p == null ){
            throw new Exception("参数<p>未指定!");
        }
        int localPort = Integer.parseInt(p);


        // 端口数量
        String c = commandLine.getOptionValue("c");
        if( c == null ){
            throw new Exception("参数<c>未指定!");
        }
        int count = Integer.parseInt(c);


        FcrSocketAddress remoteSocketAddress = FcrSocketAddress.getByString(hole);

        // 获取ip
        String ip = OsParamUtil.getLocalIpAddress(null, null);

        netManager.holePunchingOnUdp(remoteSocketAddress,localPort ,ip,count);
        process.write("打洞处理中，请稍后查看----");
        process.end();

    }

    /**
     * 打印connect
     * @param commandLine
     * @param process
     */
    private void showConnect(CommandLine commandLine, CommandProcess process) {
        String showConn = commandLine.getOptionValue("showConn");
        if(showConn == null){
            return;
        }

        Set<FcrSocketAddress> tcpSockets = netManager.getTcpSockets().keySet();
        process.write("tcp的连接列表------>\n");
        tcpSockets.forEach(e->{
            process.write(e.getAddress()+"\n");
        });

        Set<FcrSocketAddress> udpSockcets = netManager.getUdpRemoteSocketRecord().keySet();
        process.write("udp的连接列表------>\n");
        udpSockcets.forEach(e->{
            process.write(e.getAddress()+"\n");
        });
        process.end();
    }

    /**
     * 设置通信协议（原始默认协议为tcp）
     * @param commandLine
     * @param process
     */
    private void resetProtocol(CommandLine commandLine, CommandProcess process) {
        String protocol = commandLine.getOptionValue("protocol");
        if( StringUtils.isEmpty( protocol )){
            return;
        }

        // 转换为大写
        ProtocolEnum protocolEnum = ProtocolEnum.valueOf(protocol.toUpperCase());
        if( protocolEnum == null ){
            process.write("协议类型目前只支持TCP与UDP!");
            process.end();
            return;
        }
        fcrAutoConfig.setProtocol(protocolEnum);
        process.write("重置协议类型完成:"+protocol);
        process.end();
    }

    /**
     * 关闭socketServer
     * @param commandLine
     * @param process
     * @param protocolEnum
     * @param remoteHost
     * @param remotePort
     */
    private void closeSocketServer(CommandLine commandLine, CommandProcess process, ProtocolEnum protocolEnum, AtomicReference<String> remoteHost, AtomicReference<Integer> remotePort) throws Exception {
        String close = commandLine.getOptionValue("close");

        if(StringUtils.isEmpty(close)){
            return;
        }

        FcrSocketAddress remoteSocketAddress = FcrSocketAddress.getByString(close);


        switch (protocolEnum){
            case TCP:
                closeNetSocket(remoteHost.get(),remotePort.get(), process);
                break;
            case UDP:
                closeUdpServer(process,remoteSocketAddress);
                break;
            default:
                throw new Exception("不支持的网络协议:"+protocolEnum);
        }

    }

    public void closeNetSocket(String host, int port, CommandProcess process) {
        netManager.closeNetSocket(host,port,result->{
            if(result.succeeded()){
                log.debug("tcp-client close success. host:{} ,port:{}!",host,port);
                process.write("tcp-client close success, host:"+host+ " ,port:"+port);
                process.end();
            }else {
                log.debug("tcp-client close fail. host:{} ,port:{} ,err:{} !",host,port, result.cause().getMessage());
                process.write("tcp-client close fail. host:"+host+" ,port:"+port+" err:"+result.cause().getMessage());
                process.end();
            }
        });

    }

    private void closeUdpServer(CommandProcess process,FcrSocketAddress remoteSocketAddress){

        netManager.closeUdpServer(remoteSocketAddress)
                .onSuccess(e->{
                    process.write("udp socketServer close success! \n");
                    process.end();
                })
                .onFailure(f->{
                    process.write("udp socketServer close fail , msg : "+f.toString());
                    process.end();
                });
    }



    /**
     * 处理发送消息
     * @param commandLine
     * @param process
     * @param remoteHost
     * @param remotePort
     */
    private void sendMsg(CommandLine commandLine, CommandProcess process, AtomicReference<String> remoteHost, AtomicReference<Integer> remotePort) throws Exception {
        String msg = commandLine.getOptionValue("chat");

        if(StringUtils.isEmpty(msg)){
            return;
        }


        if(remoteHost.get()==null || remotePort.get()==null){
            process.write("远端地址未设置，无法发送消息. \n");
            process.end();
            return;
        }

        FcrSocketAddress remoteSocketAddress = FcrSocketAddress.builder()
                .host(remoteHost.get())
                .port(remotePort.get())
                .build();

        MsgData msgData = MsgDataUtil.createChatMsgData(msg);

        switch (fcrAutoConfig.getProtocol()){
            case TCP:
                netManager.sendMsgDataByTcpClient(msgData, remoteSocketAddress);
                break;
            case UDP:
                netManager.sendMsgDataByUdpServer(msgData, remoteSocketAddress,null);
                break;
            default:
                throw new Exception("不支持的网络协议:"+fcrAutoConfig.getProtocol());
        }

        process.end();
    }

    /**
     * 设置接收消息的远程地址
     * @param commandLine
     * @param process
     * @param remoteHost
     * @param remotePort
     */
    private void setRemoteReceiverAddress(CommandLine commandLine, CommandProcess process, AtomicReference<String> remoteHost, AtomicReference<Integer> remotePort) {
        String remote = commandLine.getOptionValue("r");
        if(StringUtils.isEmpty(remote)){
            return;
        }

        if(!remote.contains(":")) {
            process.write(" send 地址格式错误！\n");
            process.end();
        }else {
            String[] address = remote.split(":");
            remoteHost.set(address[0]);
            remotePort.set(Integer.valueOf(address[1]));
            process.write("remote address设置完成！\n");
            process.end();
        }
    }


    /**
     * 监听端口
     * @param commandLine
     * @param process
     */
    private void startListenPort(CommandLine commandLine, CommandProcess process) throws Exception {
        String port = commandLine.getOptionValue("l");
            if( StringUtils.isEmpty(port) ){
                return;
            }

            // 获取无限网卡或有线网卡的ip地址
            String ip = OsParamUtil.getLocalIpAddress(null, null);
            if(StringUtils.isEmpty(ip)){
                throw new Exception("网卡Ip获取失败，无法监听指定端口");
            }

        FcrSocketAddress localAddress = new FcrSocketAddress(ip,Integer.parseInt(port));

            switch (fcrAutoConfig.getProtocol()){
                case TCP:
                    createTcpServerFuture(process,localAddress);
                    break;
                case UDP:
                    createDefaultUdpServerFuture(process,localAddress);
                    break;
                default:
                    throw new Exception("不支持的网络协议:"+fcrAutoConfig.getProtocol());
            }

    }

    private void createTcpServerFuture(CommandProcess process, FcrSocketAddress localAddress) {
        netManager.createTcpServerFuture(localAddress.getHost(),localAddress.getPort())
                .onSuccess(a->{
                    process.write("tcp listen address:"+localAddress.getAddress()+"\n");
                    process.end();
                })
                .onFailure(f->{
                    process.write("tcp listen address:"+localAddress.getAddress()+" fail! msg:"+f);
                    process.end();
                });
    }

    private void createDefaultUdpServerFuture(CommandProcess process, FcrSocketAddress localAddress){
        netManager.createDefaultUdpServerFuture(localAddress).future()
                .onSuccess(a->{
                    process.write("udp listen address:"+ localAddress.getAddress() +" success! \n");
                    process.end();
                })
                .onFailure(f->{
                    process.write("udp listen address :"+localAddress+" fail . msg: "+f+"\n");
                    process.end();
                });
    }




    /**
     * 设置server端地址
     * @param commandLine
     * @param process
     */
    private void resetRemoteServerAddress(CommandLine commandLine, CommandProcess process) {
        String serverAddress = commandLine.getOptionValue("server");

        if(StringUtils.isEmpty(serverAddress)){
            return;
        }

        if(!serverAddress.contains(":")){
            process.write("serverAddress invalid , lost of <:> ");
            process.end();
            return;
        }
        FcrSocketAddress serverSocketAddress = FcrSocketAddress.getByString(serverAddress);
        fcrAutoConfig.setServer(serverSocketAddress);
        process.write("remote server address set success. host:"+serverSocketAddress.getHost()+", port:"+serverSocketAddress.getPort() +"\n");
        process.end();
    }

    private String outSocketAddress(SocketAddress socket){
        return socket.hostAddress()+":"+socket.port();
    }
}
